﻿using Castle.DynamicProxy;
using Microsoft.Extensions.Logging;
using MyCompany.MyProject.Common;
using MyCompany.MyProject.Repository.UnitOfWork;
using System.Reflection;

#nullable disable

namespace MyCompany.MyProject.Extensions.ServiceExtensions
{
    /// <summary>
    /// 事务拦截器
    /// </summary>
    public class TranAOP : IInterceptor
    {
        private readonly IUnitOfWorkManage unitOfWorkManage;
        private readonly ILogger<TranAOP> logger;

        public TranAOP(IUnitOfWorkManage unitOfWorkManage, ILogger<TranAOP> logger)
        {
            this.unitOfWorkManage = unitOfWorkManage;
            this.logger = logger;
        }
        /// <summary>
        /// 事务拦截方法，如果当前正在执行的方法有事务特性 则开启事务，否则正常执行
        /// </summary>
        /// <param name="invocation"></param>
        public void Intercept(IInvocation invocation)
        {
            var method = invocation.MethodInvocationTarget ?? invocation.Method;
            
            // 如果方法上有事务特性
            if (method.GetCustomAttribute<UseTranAttribute>(true) is { } uta)
            {
                try
                {
                    Before(method, uta.Propagation);

                    invocation.Proceed();

                    if (IsAsyncMethod(invocation.Method))
                    {
                        var result = invocation.ReturnValue;
                        if (result is Task)
                        {
                            Task.WaitAll(result as Task);
                        }
                    }

                    After(method);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex.Message);
                    AfterException(method);
                    throw;
                }
            }
            else
            {
                // 否则继续执行
                invocation.Proceed();
            }
        }

        /// <summary>
        /// 开启事务
        /// </summary>
        /// <param name="method"></param>
        /// <param name="propagation"></param>
        /// <exception cref="Exception"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        private void Before(MethodInfo method, Propagation propagation)
        {
            switch (propagation)
            {
                case Propagation.Required:
                    if (unitOfWorkManage.TranCount <= 0)
                    {
                        logger.LogDebug("Begin Transaction");
                        Console.WriteLine("Begin Transaction");
                        unitOfWorkManage.BeginTran(method);
                    }
                    break;
                case Propagation.Mandatory:
                    if (unitOfWorkManage.TranCount <= 0)
                    {
                        throw new Exception("事务传播机制为：[Mandatory]，当前不存在事务");
                    }
                    break;
                case Propagation.Nested:
                    logger.LogDebug("Begin Transaction");
                    Console.WriteLine("Begin Transaction");
                    unitOfWorkManage.BeginTran(method);
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(propagation), propagation, null);
            }
        }

        /// <summary>
        /// 提交事务
        /// </summary>
        /// <param name="method"></param>
        private void After(MethodInfo method)
        {
            unitOfWorkManage.CommitTran(method);
        }

        /// <summary>
        /// 回滚事务
        /// </summary>
        /// <param name="method"></param>
        private void AfterException(MethodInfo method)
        {
            unitOfWorkManage.RollbackTran(method);
        }
        /// <summary>
        /// 获取类型的默认值
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public object GetDefaultValue(Type type)
        {
            return type.IsValueType ? Activator.CreateInstance(type) : null;
        }

        /// <summary>
        /// 判断方法是否是异步方法
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        public static bool IsAsyncMethod(MethodInfo method)
        {
            return method.ReturnType == typeof(Task) ||
                method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
        }

    }
}
